package licode.unisop.oidc.authsa.service;

import cn.dev33.satoken.session.TokenSign;
import cn.dev33.satoken.stp.SaLoginModel;
import com.alibaba.fastjson.JSON;
import licode.unisop.oidc.auth.api.OidcSession;
import licode.unisop.oidc.auth.gateway.AuthorDelegate;
import licode.unisop.oidc.auth.modal.*;
import licode.unisop.oidc.authsa.exception.OidcAuthErrorCode;
import licode.unisop.oidc.authsa.stp.SaAuthUtil;
import licode.unisop.oidc.authsa.stp.SaTokenSign;
import licode.unisop.oidc.authsa.utils.TokenUtil;

import java.util.List;

/**
 * 实现授权的委托代理
 *
 * @author licode
 */
public class AuthorDelegateImpl implements AuthorDelegate {
    private SaLoginModel fromAuthorContext(AuthorContext context) {
        SaLoginModel loginModel = new SaLoginModel();
        loginModel.setUserId(context.getUid());
        loginModel.setTimeout(context.getRe() + 30);
        loginModel.setTokenSignTag(JSON.toJSONString(context));
        return loginModel;
    }

    /**
     * 普通登录流程：
     * 1）用户认证由外部完成（校验完用户的真实性，获取到用户ID）
     * 2）查询用户会话是否存在，不存在创建用户会话
     * 3）根据用户的的登录参数，生成访问令牌和刷新令牌
     * 4）登录信息返回给外部
     */
    @Override
    public OidcToken login(AuthorRequest req) {
        String loginId = req.getContext().calcLoginId();
        SaTokenSign saTokenSign;
        SaLoginModel loginModel = fromAuthorContext(req.getContext());

        saTokenSign = createTokenSign(req);
        SaAuthUtil.login(loginId, loginModel, saTokenSign, req.getContext());

        return TokenUtil.fromTokenSign(saTokenSign.getValue(), req.getContext());
    }

    @Override
    public SsoToken buildMasterSession(AuthorRequest req) {
        SaSessionProxy sessionProxy;
        SaLoginModel loginModel = fromAuthorContext(req.getContext());

        sessionProxy = SaAuthUtil.buildMasterSession(req.getContext().calcLoginId(), loginModel);

        if (null != sessionProxy) {
            return sessionProxy.getSsoToken();
        } else {
            return null;
        }
    }

    @Override
    public String loginIdFromAccessToken(AccessTokenId tokenId) {
        return SaAuthUtil.loginIdFromAccessToken(tokenId.getAccessToken());
    }

    @Override
    public String loginIdFromRefreshToken(RefreshTokenId tokenId) {
        return SaAuthUtil.loginIdFromRefreshToken(tokenId.getRefreshToken());
    }

    @Override
    public OidcToken loginSso(AuthorRequest req) {
        SaTokenSign saTokenSign;
        SaLoginModel loginModel = fromAuthorContext(req.getContext());

        saTokenSign = createTokenSign(req);
        SaAuthUtil.loginSso(req.getContext().calcLoginId(), loginModel,
                saTokenSign, req.getContext());

        return TokenUtil.fromTokenSign(saTokenSign.getValue(), req.getContext());
    }

    private SaTokenSign createTokenSign(AuthorRequest req) {
        AuthorContext context = req.getContext();
        SaSessionProxy sessionProxy;
        sessionProxy = SaAuthUtil.querySessionByLoginId(context.calcLoginId());
        checkSession(sessionProxy);
        return buildTokenSign(req);
    }

    private SaSessionProxy checkSession(SaSessionProxy sessionProxy) {
        if (null != sessionProxy) {
            if (sessionProxy.getSessionState() == 1) {
                throw OidcAuthErrorCode.SESSION_FREEZE.buildThrow();
            }
        }
        return sessionProxy;
    }

    @Override
    public OidcToken refresh(RefreshTokenId request, boolean newToken) {
        SaSessionProxy session;
        session = checkSession(SaAuthUtil.querySessionByRefreshToken(request.getRefreshToken()));
        return SaAuthUtil.refreshLogin(request.getRefreshToken(), session, newToken);
    }

    @Override
    public OidcToken getTokenInfo(AccessTokenId accessToken) {
        AuthorContext context;
        long currSeconds = System.currentTimeMillis() / 1000;
        context = SaAuthUtil.loginContextFromAccessToken(accessToken.getAccessToken());

        if (null == context || currSeconds > context.getAct()) {
            return null;
        }

        return TokenUtil.fromTokenSign(accessToken.getAccessToken(), context);
    }

    @Override
    public OidcSession getSession(UserInfoId userId) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionByLoginId(userId.getLoginId());
        return (null == session ? null : new SaOidcSession(session));
    }

    @Override
    public OidcSession getSession(AccessTokenId accessToken) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionByAccessToken(accessToken.getAccessToken());
        return (null == session ? null : new SaOidcSession(session));
    }

    @Override
    public OidcSession getSession(SsoTokenId ssoToken) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionBySsoToken(ssoToken.getSsoToken());
        return (null == session ? null : new SaOidcSession(session));
    }

    @Override
    public void kickLogin(AccessTokenId accessToken) {
        checkSession(SaAuthUtil.querySessionByAccessToken(accessToken.getAccessToken()));
        SaAuthUtil.logoutByAccessToken(accessToken.getAccessToken());
    }

    @Override
    public void kickLogin(UserInfoId userId) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionByLoginId(userId.getLoginId());
        closeAndExitSession(session);
    }

    @Override
    public void freezeLogin(AccessTokenId accessToken) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionByAccessToken(accessToken.getAccessToken());
        if (null != session) {
            session.setSessionState(1);
        }
    }

    @Override
    public void unfreezeLogin(AccessTokenId accessToken) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionByAccessToken(accessToken.getAccessToken());
        if (null != session) {
            session.setSessionState(0);
        }
    }

    @Override
    public void freezeLogin(UserInfoId userId) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionByLoginId(userId.getLoginId());
        if (null != session) {
            session.setSessionState(1);
        }
    }

    @Override
    public void unfreezeLogin(UserInfoId userId) {
            SaSessionProxy session;
            session = SaAuthUtil.querySessionByLoginId(userId.getLoginId());
            if (null != session) {
                session.setSessionState(0);
            }
    }

    @Override
    public void exitLogin(AccessTokenId accessToken) {
        SaSessionProxy session;
        session = checkSession(SaAuthUtil.querySessionByAccessToken(accessToken.getAccessToken()));
        SaAuthUtil.logoutByAccessToken(accessToken.getAccessToken(), session);
    }

    @Override
    public void exitSso(AccessTokenId accessToken) {
        SaSessionProxy session;
        session = checkSession(SaAuthUtil.querySessionByAccessToken(accessToken.getAccessToken()));
        if (null != session) {
            SaAuthUtil.logoutBySso(accessToken.getAccessToken(), session);
        }
    }

    @Override
    public void exitSession(LoginId loginId) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionByLoginId(loginId.getLoginId());
        closeAndExitSession(session);
    }

    @Override
    public void exitSession(AccessTokenId accessToken) {
        SaSessionProxy session;
        session = SaAuthUtil.querySessionByAccessToken(accessToken.getAccessToken());
        closeAndExitSession(session);
    }

    private void closeAndExitSession(SaSessionProxy session) {
        checkSession(session);
        if (null != session) {
            List<TokenSign> tokenList = session.tokenSignListCopy();
            for (TokenSign item : tokenList) {
                SaTokenSign saToken = (SaTokenSign) item;
                SaAuthUtil.logoutByAccessToken(saToken.getValue());
            }
        }
    }

    private SaTokenSign buildTokenSign(AuthorRequest req) {
        AuthorContext context = req.getContext();
        SaTokenSign saTokenSign;
        saTokenSign = SaAuthUtil.buildOauthTokenSign(context);
        return saTokenSign;
    }

}
